/* 
   Copyright (C) 2020 Captain4LK (Lukas Holzbeierlein)

   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

   3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef _ULK_MATRIX_H_

#define _ULK_MATRIX_H_

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "ULK_vector.h"

typedef float ULK_matrix_2x2[4];
typedef float ULK_matrix_2x3[6];
typedef float ULK_matrix_3x3[9];
typedef float ULK_matrix_4x4[16];

void ULK_matrix_2x2_add(ULK_matrix_2x2 out, const ULK_matrix_2x2 a, const ULK_matrix_2x2 b);
void ULK_matrix_2x2_adjoint(ULK_matrix_2x2 out, const ULK_matrix_2x2 in);
ULK_matrix_2x2 *ULK_matrix_2x2_clone(const ULK_matrix_2x2 in);
void ULK_matrix_2x2_copy(ULK_matrix_2x2 dst, const ULK_matrix_2x2 src);
ULK_matrix_2x2 *ULK_matrix_2x2_create();
float ULK_matrix_2x2_determinant(const ULK_matrix_2x2 in);
int ULK_matrix_2x2_equals(const ULK_matrix_2x2 a, const ULK_matrix_2x2 b);
int ULK_matrix_2x2_exact_equals(const ULK_matrix_2x2 a, const ULK_matrix_2x2 b);
float ULK_matrix_2x2_frob(const ULK_matrix_2x2 in);
void ULK_matrix_2x2_from_rotation(ULK_matrix_2x2 out, float rad);
void ULK_matrix_2x2_from_scaling(ULK_matrix_2x2 out, const ULK_vector_2d scaling);
ULK_matrix_2x2 *ULK_matrix_2x2_from_values(float m00, float m01, float m10, float m11);
void ULK_matrix_2x2_identity(ULK_matrix_2x2 out);
void ULK_matrix_2x2_invert(ULK_matrix_2x2 out, const ULK_matrix_2x2 in);
void ULK_matrix_2x2_LDU(ULK_matrix_2x2 L, ULK_matrix_2x2 D, ULK_matrix_2x2 U, const ULK_matrix_2x2 in);
void ULK_matrix_2x2_multiply(ULK_matrix_2x2 out, const ULK_matrix_2x2 a, const ULK_matrix_2x2 b);
void ULK_matrix_2x2_multiply_scalar(ULK_matrix_2x2 out, const ULK_matrix_2x2 in, float scale);
void ULK_matrix_2x2_rotate(ULK_matrix_2x2 out, const ULK_matrix_2x2 in, float rad);
void ULK_matrix_2x2_scale(ULK_matrix_2x2 out, const ULK_matrix_2x2 in, const ULK_vector_2d scale);
void ULK_matrix_2x2_set(ULK_matrix_2x2 out, float m00, float m01, float m10, float m11);
void ULK_matrix_2x2_subtract(ULK_matrix_2x2 out, const ULK_matrix_2x2 a, const ULK_matrix_2x2 b);
void ULK_matrix_2x2_transpose(ULK_matrix_2x2 out, const ULK_matrix_2x2 in);

void ULK_matrix_2x3_add(ULK_matrix_2x3 out, const ULK_matrix_2x3 a, const ULK_matrix_2x3 b);
ULK_matrix_2x3 *ULK_matrix_2x3_clone(const ULK_matrix_2x3 in);
void ULK_matrix_2x3_copy(ULK_matrix_2x3 dst, const ULK_matrix_2x3 src);
ULK_matrix_2x3 *ULK_matrix_2x3_create();
float ULK_matrix_2x3_determinant(const ULK_matrix_2x3 in);
int ULK_matrix_2x3_equals(const ULK_matrix_2x3 a, const ULK_matrix_2x3 b);
int ULK_matrix_2x3_exact_equals(const ULK_matrix_2x3 a, const ULK_matrix_2x3 b);
float ULK_matrix_2x3_frob(const ULK_matrix_2x3 in);
void ULK_matrix_2x3_from_rotation(ULK_matrix_2x3 out, float rad);
void ULK_matrix_2x3_from_scaling(ULK_matrix_2x3 out, const ULK_vector_2d scaling);
void ULK_matrix_2x3_from_translation(ULK_matrix_2x3 out, const ULK_vector_2d translation);
ULK_matrix_2x3 *ULK_matrix_2x3_from_values(float a, float b, float c, float d, float tx, float ty);
void ULK_matrix_2x3_identity(ULK_matrix_2x3 out);
void ULK_matrix_2x3_invert(ULK_matrix_2x3 out, const ULK_matrix_2x3 in);
void ULK_matrix_2x3_multiply(ULK_matrix_2x3 out, const ULK_matrix_2x3 a, const ULK_matrix_2x3 b);
void ULK_matrix_2x3_multiply_scalar(ULK_matrix_2x3 out, const ULK_matrix_2x3 in, float scale);
void ULK_matrix_2x3_rotate(ULK_matrix_2x3 out, const ULK_matrix_2x3 in, float rad);
void ULK_matrix_2x3_scale(ULK_matrix_2x3 out, const ULK_matrix_2x3 in, const ULK_vector_2d scale);
void ULK_matrix_2x3_set(ULK_matrix_2x3 out, float a, float b, float c, float d, float tx, float ty);
void ULK_matrix_2x3_subtract(ULK_matrix_2x3 out, const ULK_matrix_2x3 a, const ULK_matrix_2x3 b);
void ULK_matrix_2x3_translate(ULK_matrix_2x3 out, const ULK_matrix_2x3 in, const ULK_vector_2d translation);

void ULK_matrix_3x3_add(ULK_matrix_3x3 out, const ULK_matrix_3x3 a, const ULK_matrix_3x3 b);
void ULK_matrix_3x3_adjoint(ULK_matrix_3x3 out, const ULK_matrix_3x3 in);
ULK_matrix_3x3 *ULK_matrix_3x3_clone(const ULK_matrix_3x3 in);
void ULK_matrix_3x3_copy(ULK_matrix_3x3 out, const ULK_matrix_3x3 in);
ULK_matrix_3x3 *ULK_matrix_3x3_create();
float ULK_matrix_3x3_determinant(const ULK_matrix_3x3 in);
int ULK_matrix_3x3_equals(const ULK_matrix_3x3 a, const ULK_matrix_3x3 b);
int ULK_matrix_3x3_exact_equals(const ULK_matrix_3x3 a, const ULK_matrix_3x3 b);
float ULK_matrix_3x3_frob(const ULK_matrix_3x3 in);
void ULK_matrix_3x3_from_matrix_2x3(ULK_matrix_3x3 out, const ULK_matrix_2x3 in);
void ULK_matrix_3x3_from_matrix_4x4(ULK_matrix_3x3 out, const ULK_matrix_4x4 in);
void ULK_matrix_3x3_from_rotation(ULK_matrix_3x3 out, float rad);
void ULK_matrix_3x3_from_scaling(ULK_matrix_3x3 out, const ULK_vector_2d scaling);
void ULK_matrix_3x3_from_translation(ULK_matrix_3x3 out, const ULK_vector_2d translation);
ULK_matrix_3x3 *ULK_matrix_3x3_from_values(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22);
void ULK_matrix_3x3_identity(ULK_matrix_3x3 out);
void ULK_matrix_3x3_invert(ULK_matrix_3x3 out, const ULK_matrix_3x3 in);
void ULK_matrix_3x3_multiply(ULK_matrix_3x3 out, const ULK_matrix_3x3 a, const ULK_matrix_3x3 b);
void ULK_matrix_3x3_mutiply_scalar(ULK_matrix_3x3 out, const ULK_matrix_3x3 in, float scale);
void ULK_matrix_3x3_normal_from_matrix_4x4(ULK_matrix_3x3 out, const ULK_matrix_4x4 in);
void ULK_matrix_3x3_projection(ULK_matrix_3x3 out, float width, float height);
void ULK_matrix_3x3_rotate(ULK_matrix_3x3 out, const ULK_matrix_3x3 in, float rad);
void ULK_matrix_3x3_scale(ULK_matrix_3x3 out, const ULK_matrix_3x3 in, const ULK_vector_2d scale);
void ULK_matrix_3x3_set(ULK_matrix_3x3 out, float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22);
void ULK_matrix_3x3_subtract(ULK_matrix_3x3 out, const ULK_matrix_3x3 a, const ULK_matrix_3x3 b);
void ULK_matrix_3x3_translate(ULK_matrix_3x3 out, const ULK_matrix_3x3 in, const ULK_vector_2d translation);
void ULK_matrix_3x3_transpose(ULK_matrix_3x3 out, const ULK_matrix_3x3 in);

void ULK_matrix_4x4_add(ULK_matrix_4x4 out, const ULK_matrix_4x4 a, const ULK_matrix_4x4 b);
void ULK_matrix_4x4_adjoint(ULK_matrix_4x4 out, const ULK_matrix_4x4 in);
ULK_matrix_4x4 *ULK_matrix_4x4_clone(const ULK_matrix_4x4 in);
void ULK_matrix_4x4_copy(ULK_matrix_4x4 out, const ULK_matrix_4x4 in);
ULK_matrix_4x4 *ULK_matrix_4x4_create();
float ULK_matrix_4x4_determinant(const ULK_matrix_4x4 in);
int ULK_matrix_4x4_equals(const ULK_matrix_4x4 a, const ULK_matrix_4x4 b);
int ULK_matrix_4x4_exact_equals(const ULK_matrix_4x4 a, const ULK_matrix_4x4 b);
float ULK_matrix_4x4_frob(const ULK_matrix_4x4 in);
void ULK_matrix_4x4_from_rotation(ULK_matrix_4x4 out, float rad, const ULK_vector_3d axis);
void ULK_matrix_4x4_from_scaling(ULK_matrix_4x4 out, const ULK_vector_3d scaling);
void ULK_matrix_4x4_fom_translation(ULK_matrix_4x4 out, const ULK_vector_3d translation);
ULK_matrix_4x4 *ULK_matrix_4x4_from_values(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33);
void ULK_matrix_4x4_from_xrotation(ULK_matrix_4x4 out, float rad);
void ULK_matrix_4x4_from_yrotation(ULK_matrix_4x4 out, float rad);
void ULK_matrix_4x4_from_zrotation(ULK_matrix_4x4 out, float rad);
void ULK_matrix_4x4_frustum(ULK_matrix_4x4 out, float left, float right, float bottom, float top, float near, float far);
void ULK_matrix_4x4_identity(ULK_matrix_4x4 out);
void ULK_matrix_4x4_invert(ULK_matrix_4x4 out, const ULK_matrix_4x4 in);
void ULK_matrix_4x4_look_at(ULK_matrix_4x4 out, const ULK_vector_3d eye, const ULK_vector_3d center, const ULK_vector_3d up);
void ULK_matrix_4x4_multiply(ULK_matrix_4x4 out, const ULK_matrix_4x4 a, const ULK_matrix_4x4 b);
void ULK_matrix_4x4_multiply_scalar(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, float scale);
void ULK_matrix_4x4_ortho(ULK_matrix_4x4 out, float left, float right, float bottom, float top, float near, float far);
void ULK_matrix_4x4_perspective(ULK_matrix_4x4 out, float fovy, float aspect, float near, float far);
void ULK_matrix_4x4_rotate(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, float rad, const ULK_vector_3d axis);
void ULK_matrix_4x4_rotate_x(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, float rad);
void ULK_matrix_4x4_rotate_y(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, float rad);
void ULK_matrix_4x4_rotate_z(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, float rad);
void ULK_matrix_4x4_scale(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, const ULK_vector_3d scale);
void ULK_matrix_4x4_set(ULK_matrix_4x4 out, float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33);
void ULK_matrix_4x4_subtract(ULK_matrix_4x4 out, const ULK_matrix_4x4 a, const ULK_matrix_4x4 b);
void ULK_matrix_4x4_target_to(ULK_matrix_4x4 out, const ULK_vector_3d eye, const ULK_vector_3d target, const ULK_vector_3d up);
void ULK_matrix_4x4_translate(ULK_matrix_4x4 out, const ULK_matrix_4x4 in, const ULK_vector_3d translation);
void ULK_matrix_4x4_transpose(ULK_matrix_4x4 out, const ULK_matrix_4x4 in);

//Vector extension
void ULK_vector_2d_transform_matrix_2x2(ULK_vector_2d out, const ULK_vector_2d in, const ULK_matrix_2x2 m);
void ULK_vector_2d_transform_matrix_2x3(ULK_vector_2d out, const ULK_vector_2d in, const ULK_matrix_2x3 m);
void ULK_vector_2d_transform_matrix_3x3(ULK_vector_2d out, const ULK_vector_2d in, const ULK_matrix_3x3 m);
void ULK_vector_2d_transform_matrix_4x4(ULK_vector_2d out, const ULK_vector_2d in, const ULK_matrix_4x4 m);

void ULK_vector_3d_transform_matrix_3x3(ULK_vector_3d out, const ULK_vector_3d in, const ULK_matrix_3x3 m);
void ULK_vector_3d_transform_matrix_4x4(ULK_vector_3d out, const ULK_vector_3d in, const ULK_matrix_4x4 m);

void ULK_vector_4d_transform_matrix_4x4(ULK_vector_4d out, const ULK_vector_4d in, const ULK_matrix_4x4 m);

#endif
